/*  iImager (network disk imaging via iscsi)
	Copyright (C) 2009-2010 Yao Wei <njustyw@gmail.com>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#include "Q_HDDuplicator.h"
#include "Q_LogDB.h"
#include "Q_HDObject.h"
#include "Q_ISCSIFinder.h"
#include "Q_ImagerTask.h"
#include <QValidator>

static int findIP(const QString& ip, const QList<QString>& ips)
{
	for(int i = 0; i < ips.size(); i++)
	{
		if(ips[i] == ip)
			return i;
	}
	return -1;
}

static QRegExp rx("^((2[0-4]\\d|25[0-5]|[01]?\\d\\d?)\\.){3}(2[0-4]\\d|25[0-5]|[01]?\\d\\d?)$");
QRegExpValidator* validator;

HDDuplicator::HDDuplicator(QObject* object /* = 0 */) : QObject(object), QSingleton<HDDuplicator>(),
				   iSCSIDiscovery(new ISCSIFinder),taskManage(new ImagerTask)
{
    DEBUG_MSG("Enter");

	loadConfig();
	
	validator = new QRegExpValidator(rx, this);
	rackIndex = -1;
	serviceStatus = false;
	
	foreach(QString ip, liveIP)
		rackStatus[ip] = RACK_UnValid;

	connect(iSCSIDiscovery, SIGNAL(serviceStatus(bool)), this, SLOT(updateServiceStatus(bool)));
	connect(iSCSIDiscovery, SIGNAL(ipAddStatus(const QString&, bool)), this, SLOT(updateRackStatus(const QString&, bool)));
	connect(iSCSIDiscovery, SIGNAL(diskAdd(const QStringList&, const QStringList&, const QStringList&)), this, SLOT(addISCSIDisks(const QStringList&, const QStringList&, const QStringList&)));
	connect(iSCSIDiscovery, SIGNAL(diskRemove(const QStringList&, const QStringList&, const QStringList&)), this, SLOT(removeISCSIDisks(const QStringList&, const QStringList&, const QStringList&)));
}

HDDuplicator::~HDDuplicator()
{
	DEBUG_MSG("Leave");

	iSCSIDiscovery->closeDaemonConnection();

  	delete iSCSIDiscovery;
 	delete taskManage;
}

void HDDuplicator::connectToDaemonService()
{
	QTimer::singleShot(500, iSCSIDiscovery, SLOT(connectToService()));
}

void HDDuplicator::updateServiceStatus(bool flag)
{
	// If we connect the system service ImagerDaemon, then we will process iSCSI discovery   
	serviceStatus = flag;
	if(flag)
	{
		for(int i = 0; i < liveIP.size(); i++)
			iSCSIDiscovery->addIP(liveIP[i]);
	} 
	// The system service ImagerDaemon cannot be connected, we disable all UI   
	else
	{
		for(int i = 0; i < liveIP.size(); i++)
		{
			for(int j = 0; j < AllDisks[liveIP[i]].size(); j++)
				AllDisks[liveIP[i]][j]->removeDisk();
				
			updateRackStatus(liveIP[i], false);
		}
	}
}

void HDDuplicator::updateRackStatus(const QString &ip, bool flag)
{
	if(!rackStatus.contains(ip))
		return;

	allocateForRack(ip);
	if(flag)
		rackStatus[ip] = RACK_Connected;
	else
		rackStatus[ip] = RACK_Disconnected;
}

void HDDuplicator::allocateForRack(const QString& ip)
{
	int rIdx = findIP(ip, liveIP);
	if(rIdx == -1)
	{
		liveIP.push_back(ip);
		rackStatus[ip] = RACK_UnValid;
		rIdx = liveIP.size()-1;
	}

	QHash<QString, QList<HDObject*> > ::iterator it = AllDisks.find(ip);
	if(it == AllDisks.end())
	{
		QList<HDObject*> objects;
		for(int l = 0; l < ALL_DISK_NUM; l++)
			objects.push_back(new HDObject(rIdx, l, ip));

		AllDisks.insert(ip, objects);
	}
}

void HDDuplicator::addISCSIDisks(const QStringList &ipList, const QStringList& deviceList, const QStringList& targetList)
{
	if(ipList.size() != deviceList.size() && ipList.size() != targetList.size() && deviceList.size() != targetList.size())
		return;

	QMutexLocker locker(&mutex);	
	for(int i = 0; i < ipList.size(); i++)
	{
		QString ip = ipList[i];
		QString device = deviceList[i];
		QString target = targetList[i];

		//allocateForRack(ip);
		
		if(!rackStatus.contains(ip))
			continue;
		if(device.isEmpty())
			continue;

		int idx, diskIdx;

		if((idx = target.lastIndexOf("disk"))< 0)
			idx = target.lastIndexOf("slot");

		if(idx >= 0)
			diskIdx = target.mid(idx + 4).toInt() - 1;
		else
			diskIdx = 0;

		if(diskIdx < 0)
			diskIdx = 0;

		/*while(AllDisks[ip][diskIdx]->isValid() && AllDisks[ip][diskIdx]->devname != device)
		{
			diskIdx++;
			if(diskIdx == ALL_DISK_NUM)
				break;
		}*/

		if(diskIdx == ALL_DISK_NUM)
			continue;
	
		if(AllDisks[ip][diskIdx]->registerDisk(device))
		{
			if(rackStatus[ip] != RACK_Connected) 
				rackStatus[ip] = RACK_Connected;

			LogDB::instance()->addDiskFoundLog(AllDisks[ip][diskIdx]->productID, AllDisks[ip][diskIdx]->vendorID, 
				AllDisks[ip][diskIdx]->sn, AllDisks[ip][diskIdx]->total_bytes, 
				AllDisks[ip][diskIdx]->rackIndex, AllDisks[ip][diskIdx]->locationIndex, 
				AllDisks[ip][diskIdx]->hdId, AllDisks[ip][diskIdx]->hdlocId);
		}
	}
}

void HDDuplicator::removeISCSIDisks(const QStringList &ipList, const QStringList& deviceList, const QStringList& targetList)
{
	if(ipList.size() != deviceList.size() && ipList.size() != targetList.size() && deviceList.size() != targetList.size())
		return;

	QMutexLocker locker(&mutex);
	
	for(int i = 0; i < ipList.size(); i++)
	{
		QString ip = ipList[i];
		QString device = deviceList[i];
		QString target = targetList[i];
		
		if(!rackStatus.contains(ip))
			continue;
		if(device.isEmpty() || target.isEmpty())
			continue;

		int diskIdx, idx;
		if((idx = target.lastIndexOf("disk"))< 0)
			idx = target.lastIndexOf("slot");
		if(idx >= 0)
			diskIdx = target.mid(idx + 4).toInt() - 1;
		else
			diskIdx = 0;

		if(diskIdx >= ALL_DISK_NUM)
			continue;
#if 1
		for(int l = 0; l < ALL_DISK_NUM; l++)
		{
			if(AllDisks[ip][l]->devname == device)
			{
				AllDisks[ip][l]->removeDisk();
				break;
			}
		}
#endif
	
		if(AllDisks[ip][diskIdx]->devname == device)
			AllDisks[ip][diskIdx]->removeDisk();
		LogDB::instance()->addDiskRemoveLog(AllDisks[ip][diskIdx]->hdId, AllDisks[ip][diskIdx]->hdlocId);
	}
}

IRESULT HDDuplicator::connectRack(const QString& ip)
{
	int pos;
	QString _ip(ip);
	if(validator->validate(_ip, pos) !=  QValidator::Acceptable)
		return QIMAGER_PARAMERROR;

	allocateForRack(ip);
	if(!serviceStatus)
		return QIMAGER_PROCESSLATER;

	if(rackStatus[ip] == RACK_Connecting)
		goto leave;

	rackStatus[ip] = RACK_Connecting;
	iSCSIDiscovery->addIP(ip);

leave:	
	return QIMAGER_SUCCESS;
}

IRESULT HDDuplicator::disconnectRack(const QString& ip)
{
	int pos;
	QString _ip(ip);
	if(validator->validate(_ip, pos) !=  QValidator::Acceptable)
		return QIMAGER_PARAMERROR;

	if(findIP(ip, liveIP) == -1)
		return QIMAGER_RACKNOTEXIST;

	if(!serviceStatus)
		return QIMAGER_PROCESSLATER;

	if(rackStatus[ip] == RACK_Disconnected)
		goto leave;

	rackStatus[ip] = RACK_Disconnected;
	iSCSIDiscovery->removeIP(ip);

leave:	
	return QIMAGER_SUCCESS;
}

IRESULT	HDDuplicator::getWholeStatusORack(const QString& ip, RackStatus& result)
{
	int pos;
	QString _ip(ip);
	if(validator->validate(_ip, pos) !=  QValidator::Acceptable)
		return QIMAGER_PARAMERROR;

	if(findIP(ip, liveIP) == -1)
		return QIMAGER_RACKNOTEXIST;

	memset(&result, 0, sizeof(result));
	result.rackState = rackStatus[ip];
	for(int i = 0; i < ALL_DISK_NUM; i++)
	{
		HDObject* obj = AllDisks[ip][i];
		result.diskStates[i] = obj->getState();
	}
	return QIMAGER_SUCCESS;
}

IRESULT HDDuplicator::getDetailsOfDisk(const QString& ip, int location, DiskDetails& result)
{
	int pos;
	QString _ip(ip);
	if(validator->validate(_ip, pos) !=  QValidator::Acceptable)
		return QIMAGER_PARAMERROR;

	if(findIP(ip, liveIP) == -1)
		return QIMAGER_RACKNOTEXIST;
	if(location >= ALL_DISK_NUM)
		return QIMAGER_DISKNOTEXIST;

	memset(&result, 0, sizeof(result));
	HDObject* obj = AllDisks[ip][location];
	result.diskState = obj->getState();
	strncpy(result.product, obj->productID.toLatin1().data(), sizeof(result.product));
	strncpy(result.vendor, obj->vendorID.toLatin1().data(), sizeof(result.vendor));
	strncpy(result.sn, obj->sn.toLatin1().data(), sizeof(result.sn));
	result.sectorsize = obj->bytes_per_sector;
	result.totalsize = obj->total_bytes;
	return QIMAGER_SUCCESS;
}

IRESULT HDDuplicator::getDetailsOfImagingTask(QList<ImagingTaskStatus>& results)
{
	results.clear();
	QHash<HDObject*, RunningDisk>& allStates = taskManage->runningHDs;
	QHash<HDObject*, RunningDisk>::iterator it;
	for(it = allStates.begin(); it !=  allStates.end(); it++)
		results.push_back(it->runningState);

	return QIMAGER_SUCCESS;
}

IRESULT HDDuplicator::newImagingTask(const QString& ip, int location, const QString& caseno, 
									 int imagingType, int hashType)
{
	int pos;
	QString _ip(ip);
	if(validator->validate(_ip, pos) !=  QValidator::Acceptable)
		return QIMAGER_PARAMERROR;

	if(findIP(ip, liveIP) == -1)
		return QIMAGER_RACKNOTEXIST;
	if(location >= ALL_DISK_NUM)
		return QIMAGER_DISKNOTEXIST;

	HDObject* obj = AllDisks[ip][location];	
	if(obj->getState() == HD_UnValid)
		return QIMAGER_DISKNOTEXIST;

	quint64 totalMB, freeMB;
	quint64 totalCapcity = obj->getCapcity();

	/*
	if(getFreeSpace(networkDir, totalMB, freeMB) < 0 ||  freeMB <= totalCapcity/(1024*1024))
	{
		LogDB::instance()->addExceptionCopyLog(obj->rackIndex, obj->locationIndex, 
			tr("The RAID has not enough space capcity, please enlarge its capcity!"), obj->rId);
		return QIMAGER_NOTENOUGHSPACE;
	}*/

	if(obj->getState() == HD_UnValid)
		return QIMAGER_DISKNOTEXIST;

	int year = QDate::currentDate().year();
	if(!generateCorrespondingDir(networkDir, year, caseno))
		return QIMAGER_NOWRITEPERMISSION;

	QString outputName =  generateFilename(networkDir, year, caseno, obj->vendorID, obj->productID, obj->sn);
	bool ret = taskManage->readyForImaging(obj, outputName, imagingType, hashType, caseno);
	if(!ret)
		return QIMAGER_DISKALREADYIMAGING;
	ret = taskManage->startImaging(obj);
	if(!ret)
		return QIMAGER_DISKOPENERROR;
	return QIMAGER_SUCCESS;

}

IRESULT HDDuplicator::pauseImagingTask(const QString& ip, int location)
{
	int pos;
	QString _ip(ip);
	if(validator->validate(_ip, pos) !=  QValidator::Acceptable)
		return QIMAGER_PARAMERROR;

	if(findIP(ip, liveIP) == -1)
		return QIMAGER_RACKNOTEXIST;
	if(location >= ALL_DISK_NUM)
		return QIMAGER_DISKNOTEXIST;

	HDObject* obj = AllDisks[ip][location];
	HDState st = obj->getState();
	if(st == HD_Normal || st == HD_UnValid)
		return QIMAGER_DISKNOTIMAGERD;
	else if(st == HD_Paused)
		return QIMAGER_SUCCESS;
	if(!taskManage->pauseImaging(obj))
		return QIMAGER_DISKNOTIMAGERD;
	return QIMAGER_SUCCESS;
}

IRESULT HDDuplicator::continueImagingTask(const QString& ip, int location)
{
	int pos;
	QString _ip(ip);
	if(validator->validate(_ip, pos) !=  QValidator::Acceptable)
		return QIMAGER_PARAMERROR;

	if(findIP(ip, liveIP) == -1)
		return QIMAGER_RACKNOTEXIST;
	if(location >= ALL_DISK_NUM)
		return QIMAGER_DISKNOTEXIST;

	HDObject* obj = AllDisks[ip][location];
	HDState st = obj->getState();
	if(st == HD_Normal || st == HD_UnValid)
		return QIMAGER_DISKNOTIMAGERD;
	else if(st == HD_Imaging)
		return QIMAGER_SUCCESS;
	if(!taskManage->continueImaging(obj))
		return QIMAGER_DISKNOTIMAGERD;
	return QIMAGER_SUCCESS;
}

IRESULT HDDuplicator::cancelImagingTask(const QString& ip, int location)
{
	int pos;
	QString _ip(ip);
	if(validator->validate(_ip, pos) !=  QValidator::Acceptable)
		return QIMAGER_PARAMERROR;

	if(findIP(ip, liveIP) == -1)
		return QIMAGER_RACKNOTEXIST;
	if(location >= ALL_DISK_NUM)
		return QIMAGER_DISKNOTEXIST;

	HDObject* obj = AllDisks[ip][location];
	HDState st = obj->getState();
	if(st == HD_Normal || st == HD_UnValid)
		return QIMAGER_DISKNOTIMAGERD;
	if(!taskManage->cancelImaging(obj))
		return QIMAGER_DISKNOTIMAGERD;
	return QIMAGER_SUCCESS;
}
